#include "WebsocketClient.h"

#include <libwebsockets.h>
#include <cstring>
#include <app/MessageDef/MessageDefHelper.h>
#include <lib/support/logging/CHIPLogging.h>

#include "Message.h"



namespace {
int OnWebSocketCallback(lws *wsi, lws_callback_reasons reason, void *user, void *in, size_t len);

constexpr uint16_t kMaxMessageBufferLen = 8192;
constexpr lws_protocols kWsProtocols[] = {
        {"ws", OnWebSocketCallback, 0, kMaxMessageBufferLen, 0, nullptr, 0},
        LWS_PROTOCOL_LIST_TERM,
};

int OnWebSocketCallback(lws *wsi, lws_callback_reasons reason, void *user, void *in, size_t len) {
    switch (reason) {
        case LWS_CALLBACK_CLIENT_RECEIVE: {
            ChipLogDetail(DeviceLayer, "LwsCallback: Client Receive");
            if (const auto protocol = lws_get_protocol(wsi); !protocol) {
                ChipLogError(DeviceLayer, "Failed to retrieve the protocol");
                return -1;
            }
            char buffer[len + 1];
            memset(buffer, 0x00, sizeof buffer);
            memcpy(buffer, in, len);
            const auto client = static_cast<WebSocketClient *>(user);
            client->OnReceive(wsi, buffer);
            break;
        }

        case LWS_CALLBACK_CLIENT_WRITEABLE: {
            ChipLogDetail(DeviceLayer, "LwsCallback: Client Writeable");
            const auto client = static_cast<WebSocketClient *>(user);
            client->OnWrite(wsi);
            break;
        }

        case LWS_CALLBACK_CONNECTING: {
            const auto client = static_cast<WebSocketClient *>(user);
            client->OnStateChanged(WebSocketClient::State::Connecting);
            ChipLogDetail(DeviceLayer, "LwsCallback: Connecting");
            break;
        }

        case LWS_CALLBACK_CLIENT_ESTABLISHED: {
            const auto client = static_cast<WebSocketClient *>(user);
            client->OnStateChanged(WebSocketClient::State::Established);
            ChipLogDetail(DeviceLayer, "LwsCallback: ClientEstablished");
            break;
        }

        case LWS_CALLBACK_CLIENT_CLOSED: {
            const auto client = static_cast<WebSocketClient *>(user);
            client->OnStateChanged(WebSocketClient::State::Closed);
            ChipLogDetail(DeviceLayer, "LwsCallback: Client Closed");
            break;
        }

        case LWS_CALLBACK_WSI_DESTROY: {
            const auto client = static_cast<WebSocketClient *>(user);
            client->OnStateChanged(WebSocketClient::State::Destroy);
            ChipLogDetail(DeviceLayer, "LwsCallback: Wsi Destroy");
            break;
        }

    case LWS_CALLBACK_CLIENT_RECEIVE_PONG:
        ChipLogProgress(DeviceLayer, "LwsCallback: received poing");
        break;

    case LWS_CALLBACK_TIMER:
        {
            lws_set_timer_usecs(wsi, 30'000'000);
            uint8_t buffer[LWS_PRE];
            lws_write(wsi, &buffer[LWS_PRE], 0, LWS_WRITE_PING);
            ChipLogProgress(DeviceLayer, "LwsWrite: write ping");
            break;
        }

        default:
            ChipLogProgress(DeviceLayer, "reason=%d user = %p", reason, user);
            break;
    }
    return 0;
}

}

void WebSocketClient::OnStateChanged(WebSocketClient::State state) {
//    ChipLogDetail(DeviceLayer, "WebsocketState: %d", state);
    mState = state;
    mDelegate->OnWebSocketStateChanged(mState);
    if (mState == State::Destroy) {
        mRunning = false;
    }
}

int WebSocketClient::Run() {
    lws_context_creation_info ctx_info{};
    memset(&ctx_info, 0x00, sizeof(ctx_info));
    ctx_info.port = CONTEXT_PORT_NO_LISTEN;
    ctx_info.iface = nullptr;
    ctx_info.pt_serv_buf_size = kMaxMessageBufferLen;
    ctx_info.protocols = kWsProtocols;
    ctx_info.gid = static_cast<gid_t>(-1);
    ctx_info.uid = static_cast<uid_t>(-1);

    static constexpr lws_retry_bo_t retry = {
        .secs_since_valid_ping = 60,
        .secs_since_valid_hangup = 60,
    };
    ctx_info.retry_and_idle_policy = &retry;
    lws_context *context = lws_create_context(&ctx_info);
    if (context == nullptr) {
        ChipLogError(DeviceLayer, "WebSocketClient: lws_create_context() err");
        return -1;
    }

    struct lws_client_connect_info conn_info{};
    memset(&conn_info, 0x00, sizeof(conn_info));
    conn_info.context = context;
    conn_info.address = mRemoteAddr.c_str();
    conn_info.port = mRemotePort;
    conn_info.path = mRemotePath.c_str();
    conn_info.userdata = this;
    ChipLogProgress(DeviceLayer, "this = %p", this);

    char addr_port[32];
    sprintf(addr_port, "%s:%d", mRemoteAddr.c_str(), mRemotePort);
    conn_info.host = addr_port;
    conn_info.origin = addr_port;
    conn_info.protocol = kWsProtocols[0].name;

    lws *client = lws_client_connect_via_info(&conn_info);
    if (client == nullptr) {
        ChipLogError(DeviceLayer, "WebSocketClient: lws_client_connect_via_info() err");
        return -1;
    }
    mRunning = true;
    lws_set_timer_usecs(client, 10'000'000);

    while (mRunning) {
        int timeout = mState == State::Established ? -1 : 2000;
        if (lws_service(context, timeout) < 0) {
            mRunning = false;
        }

        if (mState == State::Established) {
            std::lock_guard lock(mMutex);
            if (!mMessageQueue.empty()) {
                ChipLogProgress(DeviceLayer, "lws_callback_on_writable");
                lws_callback_on_writable(client);
            }
        }
    }

    ChipLogError(DeviceLayer, "lws_context_destroy");
    lws_context_destroy(context);
    return 0;
}

void WebSocketClient::Send(const std::string &msg) {
    std::lock_guard lock(mMutex);
    mMessageQueue.emplace_back(msg);
}

void WebSocketClient::OnWrite(void *client) {
    std::lock_guard lock(mMutex);
    for (auto &msg: mMessageQueue) {
        ChipLogProgress(DeviceLayer, "OnWrite <<-- %s", msg.c_str());
        auto *buffer = static_cast<unsigned char *>(malloc(LWS_PRE + msg.size()));
        memcpy(&buffer[LWS_PRE], (void *) msg.c_str(), msg.size());
        lws_write(static_cast<struct lws *>(client), &buffer[LWS_PRE], msg.size(), LWS_WRITE_TEXT);
    }
    mMessageQueue.clear();
}


void WebSocketClient::OnReceive(void *client, char *msg) const
{
    ChipLogProgress(DeviceLayer, "websocket <<-- %s", msg);
    if (mDelegate == nullptr) {
        return;
    }
    Json::Value root;
    if (!Json::Reader().parse(msg, root)) {
        ChipLogError(DeviceLayer, "json parse failed: %s", msg);
        return;
    }
    mDelegate->OnWebSocketMessageReceived(root);
}

WebSocketClient::WebSocketClient(const std::string &url, Delegate *delegate) {
    std::string tmp = url;
    this->mDelegate = delegate;

    if (const int split2 = static_cast<int>(url.find_first_of('/')); split2 < 0) {
        mRemotePath = "/";
    } else {
        mRemotePath = url.substr(split2, url.length() - split2);
        tmp = url.substr(0, split2);
    }

    if (const int split1 = static_cast<int>(tmp.find_first_of(':')); split1 < 0) {
        mRemoteAddr = tmp;
        mRemotePort = 80;
    } else if (split1 == 0) {
        mRemoteAddr = "127.0.0.1";
        mRemotePort = static_cast<uint16_t>(stoi(tmp.substr(1, tmp.length() - 1)));
    } else {
        mRemoteAddr = tmp.substr(0, split1);
        mRemotePort = static_cast<uint16_t>(stoi(tmp.substr(split1 + 1, tmp.length() - split1 - 1)));
    }
}

void WebSocketClient::SendMessage(const Json::Value &root) {
    Send(Json::FastWriter().write(root));
}
